﻿using System.Drawing;
using DrPoint = System.Drawing.Point;
using DrSize = System.Drawing.Size;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;
using OpenCvSharp;
using CVSize = OpenCvSharp.Size;
using CVPoint = OpenCvSharp.Point;
using Sdcb.PaddleOCR.Models.LocalV3;
using Sdcb.PaddleOCR.Models;
using Sdcb.PaddleInference;
using Sdcb.PaddleOCR;

namespace SUNRPA.CONTROLS.IMPL.util {
    public class ImageUtil {
        public static PaddleOcrAll ocrEngin;
        static ImageUtil() { 
            //本地模型初始化(目前只支持本地)
            FullOcrModel model = LocalFullModels.ChineseV3;
            ocrEngin = new PaddleOcrAll(model, PaddleDevice.Mkldnn()){
                AllowRotateDetection = true, /* 允许识别有角度的文字 */ 
                Enable180Classification = false, /* 允许识别旋转角度大于90度的文字 */
            };

            //在线模型初始化
            //FullOcrModel model = await OnlineFullModels.EnglishV3.DownloadAsync();
        }

        //c#中intptr相当于int但是使用intptr来封装一个指针即代表的是一个句柄
        //判断图形里是否存在另外一个图形 并返回所在位置
        //offset溶差0~120数值越大，容许相差越大（0为完全相同，一般50到100可以称：相似）</param>
        //return 坐标
        //目前值支持原图和目标图都是矩形。不规则形状后续开发
        public static DrPoint GetPicturePos(Bitmap source, Bitmap part, int range){
            //获取原图和目标图的像素
            int sourceWidth = source.Width;
            int sourceHeight = source.Height;
            int partWidth = part.Width;
            int partHeight = part.Height;
           
            //获取像素深度
            int sourceDepth = Image.GetPixelFormatSize(source.PixelFormat);
            int partDepth = Image.GetPixelFormatSize(part.PixelFormat);

            //获取步长
            int sstep = sourceDepth / 8;
            int pstep = partDepth / 8;

            Bitmap sourceBitmap = new Bitmap(sourceWidth, sourceHeight);
            Graphics graphics = Graphics.FromImage(sourceBitmap);
            //复制图像
            graphics.DrawImage(source, new Rectangle(0, 0, sourceWidth, sourceHeight));
            //释放资源
            graphics.Dispose();
            //锁定资源到内存主要是为了快速直接的修改bitmap的像素信息，也可以用setpixelformat来更改
            BitmapData sourceData = sourceBitmap.LockBits(new Rectangle(0, 0, sourceWidth, sourceHeight), 
                ImageLockMode.ReadWrite, source.PixelFormat);
            byte[] sourceByte = new byte[sourceDepth / 8 * sourceHeight * sourceWidth];
            //非托管内存复制到托管内存
            Marshal.Copy(sourceData.Scan0, sourceByte, 0, sourceByte.Length); 
            //解锁
            sourceBitmap.UnlockBits(sourceData);
            sourceBitmap.Dispose();
            
            //需要考虑是否需要使用Marshal.FreeHGlobal来释放bitdata的内存空间
            Bitmap partBitmap = new Bitmap(partWidth, partHeight);
            graphics = Graphics.FromImage(partBitmap);
            graphics.DrawImage(part, new Rectangle(0, 0, partWidth, partHeight));
            graphics.Dispose();

            BitmapData partData = partBitmap.LockBits(new Rectangle(0, 0, partWidth, partHeight), 
                ImageLockMode.ReadWrite, partBitmap.PixelFormat);

            byte[] partByte = new byte[partDepth / 8 * partHeight * partWidth];
            Marshal.Copy(partData.Scan0, partByte, 0, partByte.Length);
            
            partBitmap.UnlockBits(partData);
            partBitmap.Dispose();
            for (int i = 0; i < sourceHeight; i++){
                if(sourceHeight - i < partHeight) { 
                    return new DrPoint(-1, -1);
                }
                List<int> preMatch = new List<int>();
                int tmps = i;
                bool isMatch = true;
                for(int y = 0; y < partHeight; y++) { 
                    List<int> matchXst = GetMatchX(0, sourceWidth, partWidth, tmps, y, sstep, pstep, sourceByte, partByte, range);
                    if(matchXst.Count <= 0) { 
                        isMatch = false;
                        break;
                    } else {
                        tmps += 1;
                        if(y == 0) { 
                            preMatch = matchXst;
                        } else { 
                            //取交集
                            preMatch = preMatch.Intersect(matchXst).ToList();
                            if(preMatch.Count <=0) { 
                                isMatch = false;
                                break;
                            }
                        }
                    }
                }
                //只取第一个
                if(isMatch) {
                    int x = preMatch[0];
                    return new DrPoint(x, i);
                }
            }
            return new DrPoint(-1, -1);
        }

        public static List<int> GetMatchX(int px, int sourceWidth, int partWidth, int sy, int py, int sstep, int pstep, byte[] sourceByte, byte[] partByte, int range) { 
            List<int> matchXs = new List<int>();
            //kmp算法优化
            for(; px < sourceWidth; px++) {
                if(sourceWidth - 1 < partWidth) { 
                    return matchXs;
                }
                if(px == 250) { 
                    px = 250;
                }
                int pixalIndex = (sy * sourceWidth + px) * sstep;
                bool indexMatch = true;
                int rowPix = (py * partWidth) * pstep;
                for(int y = 0; y < partWidth; y++) { 
                    int pixalIndex2 = rowPix + y*pstep; 
                    Color sc = GetPixelColor(sstep, pixalIndex, sourceByte);
                    Color pc = GetPixelColor(pstep, pixalIndex2, partByte);
                    bool isMatch = ScanColor(sc, pc, range);
                    if(isMatch) { 
                        pixalIndex += sstep;
                    } else { 
                        indexMatch = false;
                        break;
                    }
                }
                if(indexMatch) { 
                    matchXs.Add(px);
                }
            }
            return matchXs;
        }

        public static Color GetPixelColor(int depth, int offset, byte[] pixelByte){
            Color clr = Color.Empty;
            if (depth == 4) // 获得32 bpp红色，绿色，蓝色和Alpha
            {
                byte b = pixelByte[offset];
                byte g = pixelByte[offset + 1];
                byte r = pixelByte[offset + 2];
                byte a = pixelByte[offset + 3]; // a
                clr = Color.FromArgb(a, r, g, b);
            }

            if (depth == 2) // 获得24 bpp红色，绿色和蓝色
            {
                byte b = pixelByte[offset];
                byte g = pixelByte[offset + 1];
                byte r = pixelByte[offset + 2];
                clr = Color.FromArgb(r, g, b);
            }

            if (depth == 1) // 获得8 bpp
            {
                byte c = pixelByte[offset];
                clr = Color.FromArgb(c, c, c);
            }
            return clr;
        }

        //检查色彩(可以根据这个更改比较方式)
        private static bool ScanColor(Color purrentlyColor, Color pompareColor, int offset){
            int _R = purrentlyColor.R;
            int _G = purrentlyColor.G;
            int _B = purrentlyColor.B;
            return (_R <= pompareColor.R + offset && _R >= pompareColor.R - offset) 
                && (_G <= pompareColor.G + offset && _G >= pompareColor.G - offset) 
                && (_B <= pompareColor.B + offset && _B >= pompareColor.B - offset);
        }

        //获取目标图片的中心左边
        //weight目标图片的宽
        //height目标图片的长
        public static DrPoint ReCalculatePos(DrPoint absPos, DrPoint relativePos, int weight, int height) {
            int x = absPos.X + relativePos.X + weight / 2;
            int y = absPos.Y + relativePos.Y + height / 2;
            return new DrPoint(x, y);
        }

        //获取主屏幕的整个屏幕截图
        public static Bitmap TakeScreenShoot() { 
            DrSize screenSize = Screen.AllScreens[0].Bounds.Size;
            DrPoint upperScreenPoint = new DrPoint(0, 0);
            DrPoint upperDestinationPoint = new DrPoint(0, 0);
            Bitmap desktopImage = new Bitmap(screenSize.Width, screenSize.Height);
            Graphics graphics = Graphics.FromImage(desktopImage);
            graphics.CopyFromScreen(upperScreenPoint, upperDestinationPoint, screenSize);
            graphics.Dispose();
            return desktopImage;
        }

        //获取屏幕部分区域截图
        public static Bitmap TakeAreaCapture(System.Windows.Rect area){
            //获取整个屏幕位图
            Bitmap screenBitMap = TakeScreenShoot();
            Bitmap cutArea = new Bitmap((int)area.Width, (int)area.Height);
            Graphics g = Graphics.FromImage(cutArea);
            try{
                //在原始的屏幕截图ScreenBitmap上 截取 用户选择范围大小的区域   绘制到上面的空图
                //绘制完后，这个空图就是我们想要的截取的图片
                //参数1  原图
                //参数2  在空图上绘制的范围区域
                //参数3  原图的截取范围
                //参数4  度量单位
                g.DrawImage(screenBitMap, new Rectangle(0, 0, cutArea.Width, cutArea.Height),
                    new Rectangle((int)area.X, (int)area.Y, (int)area.Width, (int)area.Height), GraphicsUnit.Pixel);
                return cutArea;
            }finally{
                g.Dispose();
                screenBitMap.Dispose();
            }
        }

        //通过窗口句柄获取窗口截图
        //由于窗口可能会被遮挡等问题，所以使用复制窗口内容的方式来截图
        public static Bitmap TakeWindowShot(IntPtr intPtr) { 
            //获取窗口DC(设备环境),PS:由于windiows不允许直接操作硬件。所以屏幕操作都是通过DC完成，每个窗口都对应一个DC相当于缓冲
            IntPtr hscrdc = User32Utils.GetWindowDC(intPtr);
            Rectangle windowRect;
            //获取窗口宽高
            User32Utils.GetWindowRect(intPtr, out windowRect);
            int width = Math.Abs(windowRect.Width - windowRect.X);
            int height = Math.Abs(windowRect.Height - windowRect.Y);
            //创建句柄（窗口内容）
            IntPtr hbitmap = GDIUtils.CreateCompatibleBitmap(hscrdc, width, height);
            //创建新窗口DC
            IntPtr hmemdc = GDIUtils.CreateCompatibleDC(hscrdc);
            //把一个对象(位图、画笔、画刷等)选入指定的设备描述表。新的对象代替同一类型的老对象
            //吧新建测窗口图像放入新建得dc中
            GDIUtils.SelectObject(hmemdc, hbitmap);
            //将窗口打印到新建的窗口设备中
            User32Utils.PrintWindow(intPtr, hmemdc, 0);
            //从句柄中获取位图
            Bitmap bmp = Image.FromHbitmap(hbitmap);
            GDIUtils.DeleteDC(hscrdc);
            GDIUtils.DeleteDC(hmemdc);
            GDIUtils.DeleteObject(hbitmap);
            return bmp;
        }

        //获取图片上所有文字区域
        public static List<RotatedRect> GetTextArea(Mat dilation) { 
            List<RotatedRect> rcts = new List<RotatedRect>();
            CVPoint[][] counters;
            HierarchyIndex[] vec4Is;
            Cv2.FindContours(dilation, out counters, out vec4Is, RetrievalModes.CComp, ContourApproximationModes.ApproxSimple, new CVPoint(0,0));
            for (int i = 0; i < counters.Count(); i++){
                //计算当前轮廓的面积
                double area = Cv2.ContourArea(counters[i]);
 
                //面积小于1000的全部筛选掉
                if (area < 500) { 
                    continue;
                }
                /*double epsilon = 0.001*Cv2.ArcLength(counters[i], true);
                Mat approx = new Mat();
                Cv2.ApproxPolyDP(Mat.FromArray(counters[i]) , approx, epsilon, true);*/

                //找到最小矩形，该矩形可能有方向
                RotatedRect rect = Cv2.MinAreaRect(counters[i]);
 
                //计算高和宽
                int width = rect.BoundingRect().Width;
                int height = rect.BoundingRect().Height;
 
                //筛选那些太细的矩形，留下扁的
                if (height > width * 1.2) { 
                    continue;
                }
                   
                //符合条件的rect添加到rects集合中
                rcts.Add(rect);
 
            }
            return rcts;
        }

        //图像预处理，使得可以更加快速分辨出字体区域
        private static Mat ProcessPic(Mat src){
            Mat binary = ConvertToBinary(src);
 
            //膨胀和腐蚀
            Mat ele1 = Cv2.GetStructuringElement(MorphShapes.Rect, new CVSize(30, 9));
            //控制高度设置可以控制上下行的膨胀程度，例如3比4的区分能力更强,但也会造成漏检
            Mat ele2 = Cv2.GetStructuringElement(MorphShapes.Rect, new CVSize(25, 8));
            //第一次膨胀
            Mat dst1 = new Mat();
            Cv2.MorphologyEx(src: binary, dst: dst1, op: MorphTypes.Dilate, element: ele2);
            //腐蚀一次
            Mat dst2 = new Mat();
            Cv2.MorphologyEx(src: dst1, dst: dst2, op: MorphTypes.Erode, element: ele1);
            //再次膨胀，让轮廓明显一些
            Mat dst3 = new Mat();
            Cv2.MorphologyEx(src: dst2, dst: dst3, op: MorphTypes.Dilate, element: ele2);
            return dst3;
        }

        public static Mat ConvertToBinary(Mat src){ 
            Mat gray = src.CvtColor(ColorConversionCodes.BGR2GRAY);
            //二值化
            Mat binary = new Mat();
            Cv2.Threshold(gray, binary, 100, 255, ThresholdTypes.Binary | ThresholdTypes.Otsu);
            return binary;
        }

        //圈出图片指定位置
        public static Mat LineSpecifyArea(List<RotatedRect> rcts, Mat srcPic) { 
            Mat tempMat = srcPic.Clone();
            foreach(RotatedRect rect in rcts){
                Point2f[] ps = rect.Points();
                for(int i=0; i < 4; i++) { 
                    Cv2.Line(tempMat, (int)ps[i].X, (int)ps[i].Y, (int)ps[(i + 1) % 4].X, (int)ps[(i + 1) % 4].Y, new Scalar(0,255,0), 2);
                }
            }
            return tempMat;
        }

        //旋转矩形裁切
        public static List<Bitmap> RotatedRectCut(Bitmap srcPic, List<RotatedRect> rcts) {
            List<Bitmap> results = new List<Bitmap>();
            foreach(RotatedRect rotatedRect in rcts) { 
                Point2f[] points=rotatedRect.Points();
                int topLeftIndex=0;
                double topLeftR=points[0].X*points[0].X+points[0].Y*points[0].Y;
                for(int i=1;i<4;i++){
                    double r=points[i].X*points[i].X+points[i].Y*points[i].Y;
                    if(r<topLeftR){
                        topLeftIndex=i;
                        topLeftR=r;
                     }
                }
                double x1=points[(topLeftIndex+1)%4].X-points[topLeftIndex].X,y1=points[(topLeftIndex+1)%4].Y-points[topLeftIndex].Y;
                double x2=points[(topLeftIndex+3)%4].X-points[topLeftIndex].X,y2=points[(topLeftIndex+3)%4].Y-points[topLeftIndex].Y;
                double vX1=x1,vY1=y1,vX2=x2,vY2=y2;
                int width=(int)Math.Sqrt(vX1*vX1+vY1*vY1),height=(int)Math.Sqrt(vX2*vX2+vY2*vY2);
                Mat ret=new Mat(new CVSize(width,height),MatType.CV_8UC3);
                var indexer1=BitMap2Mat(srcPic).GetGenericIndexer<Vec3b>();
                var indexer2=ret.GetGenericIndexer<Vec3b>();
                for(int j=0;j<ret.Height;j++){
                    for(int i=0;i<ret.Width;i++){
                        double kx=(double)i/width,ky=(double)j/height;
                        int x=(int)(points[topLeftIndex].X+kx*vX1+ky*vX2),y=(int)(points[topLeftIndex].Y+kx*vY1+ky*vY2);
                        indexer2[j,i]=indexer1[y,x];
                    }
                }
                 results.Add(Mat2BitMap(ret));
            }
            return results;
        }

        //正常矩形裁切
        public static List<Bitmap> RectCut(Bitmap srcPic, List<RotatedRect> rcts) {
            List<Bitmap> results = new List<Bitmap>();

            return results;
        }

        //paddleOcr检测文本
        //PS:若检测文本区域过于大，不够细粒度，可以考虑以上使用Opencv裁切图片后再识别
        //TODO:暂时不支持位置选取，后续会加上多识别结果位置选取功能
        public static PaddleOcrResult PaddleOCR(Mat src) { 
            return ocrEngin.Run(src);
        }

        public static PaddleOcrResult PaddleOCR(string pidPath) { 
            using(Mat src = Cv2.ImRead(pidPath)) { 
                return PaddleOCR(src);
            }
        }

        //高亮某个区域
        public static Bitmap HighLightArea(Bitmap src, List<DrPoint> points) { 
            Pen pen = new Pen(Color.Orange,3);
            Bitmap tmp = new Bitmap(src);
            using(Graphics g = Graphics.FromImage(tmp)) { 
                g.DrawPolygon(pen, points.ToArray());
            }
            return tmp;
        }
        //高亮某个区域
        public static void HighLightArea(Bitmap src, DrPoint point, int w, int h) { 
            Pen pen = new Pen(Color.Red,2);
            Rectangle rect = new Rectangle(point,new DrSize(w,h));
            Graphics g = Graphics.FromImage(src);
            //将路径绘制在新的BItmap上，之后要释放
            g.DrawRectangle(pen, rect);
            g.Dispose();
            pen.Dispose();
        }

        /*/// <summary>
        /// 旋转校正
        /// </summary>
        /// <param name="imageInput"></param>
        /// <returns></returns>
        private static Image<Gray, Byte> randon(Image<Gray, Byte> imageInput)//图像投影旋转法倾斜校正
        {
            int nwidth = imageInput.Width;
            int nheight = imageInput.Height;
            int sum;
            int SumOfCha;
            int SumOfChatemp = 0;
            int[] sumhang = new int[nheight];
            Image<Gray, Byte> resultImage = imageInput;
            Image<Gray, Byte> ImrotaImage;
            //20度范围内的调整
            for (int ang = -20; ang < 20; ang = ang + 1)
            {
                ImrotaImage = imageInput.Rotate(ang, new Gray(1));
                for (int i = 0; i < nheight; i++)
                {
                    sum = 0;
                    for (int j = 0; j < nwidth; j++)
                    {
                        sum += ImrotaImage.Data[i, j, 0];
                    }
                    sumhang[i] = sum;
                }
                SumOfCha = 0;
                for (int k = 0; k < nheight - 1; k++)
                {
                    SumOfCha = SumOfCha + (Math.Abs(sumhang[k] - sumhang[k + 1]));
                }
                if (SumOfCha > SumOfChatemp)
                {
                    resultImage = ImrotaImage;
                    SumOfChatemp = SumOfCha;
                }
            }
            return resultImage;
        }*/

        //bitMap转mat
        public static Mat BitMap2Mat(Bitmap src) {
            return OpenCvSharp.Extensions.BitmapConverter.ToMat(src);
        }

        //mat转bitmap
        public static Bitmap Mat2BitMap(Mat src) {
            return OpenCvSharp.Extensions.BitmapConverter.ToBitmap(src);
        }

    }
}
